library(tidyverse)
library(dplyr)
library(ggplot2)
library(rgdal)
library(tmap)
library(readxl)
library(ggrepel)
library(ggthemes)
library(scales)
library(sf)
library(raster)
library(rgeos)
library(ggmap)
library(DT)
library(devtools)
ggmap::register_google(key = "AIzaSyB1-MjiXEIrgdT3FbflMLc8EUaQXVG3XVY")
devtools::install_github("rstudio/leaflet")

Parking Violations in NYC

fines <- read_excel("ParkingViolationCodes_January2020.xlsx")
1. Data exploration
head(df)
colnames(df)
 [1] "Summons Number"                    "Plate ID"                          "Registration State"               
 [4] "Plate Type"                        "Issue Date"                        "Violation Code"                   
 [7] "Vehicle Body Type"                 "Vehicle Make"                      "Issuing Agency"                   
[10] "Street Code1"                      "Street Code2"                      "Street Code3"                     
[13] "Vehicle Expiration Date"           "Violation Location"                "Violation Precinct"               
[16] "Issuer Precinct"                   "Issuer Code"                       "Issuer Command"                   
[19] "Issuer Squad"                      "Violation Time"                    "Time First Observed"              
[22] "Violation County"                  "Violation In Front Of Or Opposite" "House Number"                     
[25] "Street Name"                       "Intersecting Street"               "Date First Observed"              
[28] "Law Section"                       "Sub Division"                      "Violation Legal Code"             
[31] "Days Parking In Effect"            "From Hours In Effect"              "To Hours In Effect"               
[34] "Vehicle Color"                     "Unregistered Vehicle?"             "Vehicle Year"                     
[37] "Meter Number"                      "Feet From Curb"                    "Violation Post Code"              
[40] "Violation Description"             "No Standing or Stopping Violation" "Hydrant Violation"                
[43] "Double Parking Violation"          "issue_date"                        "year"                             
[46] "month"                             "day"                              
df <- dplyr::select(df, -"Violation Description")
colnames(fines) <- str_to_title(colnames(fines))
fines <- fines[c(1:3)]
colnames(fines)[3] <- "Fine Amount $"
head(fines)

Violation 38 [FAIL TO DSPLY MUNI METER RECPT] and 69 [FAIL TO DISP. MUNI METER RECPT] look like the same infraction. The fine is $65 for both. I’d rename one as the other and aggregate them as the same violation.

fines$`Violation Description`[69] <- "FAIL TO DSPLY MUNI METER RECPT"
df <- merge(x = df, y = fines, by = "Violation Code", all.x = TRUE)
a) Violation Code and Fine Amounts

Add the violation code descriptions and fine amounts to the data file. Provide a visual overview of the top 10 most common types of violations (feel free to group them into categories if reasonable). Compare how this ranking differs if we focus on the total amount of revenue generated.

Top 10 Violations

one_a <- df %>%
  dplyr::select(`Violation Description`) %>%
  group_by(`Violation Description`) %>%
  count() %>%
  ungroup() %>%
  arrange(desc(n)) %>%
  head(10)
legend_ord <- levels(with(one_a, reorder(`Violation Description`, desc(n))))
one_a %>%
  ggplot(aes(x = reorder(`Violation Description`, desc(n)), y = n, fill = `Violation Description`)) +
  geom_col(alpha = 0.8) +
  scale_fill_brewer(palette = "RdBu", breaks = legend_ord) +
  
  labs(
  title = "Top 10 most common types of violations in NYC",
  x = "",
  y = "Number of Violations",
  x.axis = "",
  caption = "Source: data.cityofnewyork.us"
  ) +
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect(),
   
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    
    legend.key = element_rect(fill = "white", colour = "white"),
    legend.background = element_rect()
    ) + 
  
  scale_y_continuous(labels = comma)

Top 10 Violations for revenue

one_a_2 <- df %>%
  dplyr::select(`Violation Description`, `Fine Amount $`) %>%
  group_by(`Violation Description`,`Fine Amount $`) %>% 
  summarize(`Total Fines Amount $` = sum(`Fine Amount $`)) %>%
  arrange(desc(`Total Fines Amount $`)) %>%
  head(10)
`summarise()` has grouped output by 'Violation Description'. You can override using the `.groups` argument.
legend_ord2 <- levels(with(one_a_2, reorder(`Violation Description`, desc(`Total Fines Amount $`))))
one_a_2 %>%
  ggplot(aes(x = reorder(`Violation Description`, desc(`Total Fines Amount $`)), y = `Total Fines Amount $`, fill = `Violation Description`)) +
  geom_col(alpha = 0.8) +
  scale_fill_brewer(palette = "RdBu", breaks = legend_ord2) +
  
  labs(
  title = "Top 10 highest violations in NYC per revenue",
  x = "",
  y = "Dollar ($)",
  x.axis = "",
  caption = "Source: data.cityofnewyork.us"
  ) +
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect(),
   
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    
    legend.key = element_rect(fill = "white", colour = "white"),
    legend.background = element_rect()
    ) + 
  
  scale_y_continuous(labels = comma)

b) Average amount of fine by vehicle

Compare the average amount of fine by vehicle color, vehicle year, and vehicle plate type [Hint: it is sufficient to restrict your attention to commercial (COM) and passenger (PAS) vehicles]? Briefly describe your findings.

Vehicle Color

one_b_1 <- df %>%
  dplyr::select(`Vehicle Color`, `Fine Amount $`) %>%
  drop_na(`Fine Amount $`) %>%
  group_by(`Vehicle Color`) %>%
  add_count(`Vehicle Color`) %>%
  filter(n > 10) %>%
  summarize(`Average Fine` = mean(`Fine Amount $`)) %>%
  arrange(desc(`Average Fine`)) %>%
  head(10)
legend_ord3 <- levels(with(one_b_1, reorder(`Vehicle Color`, desc(`Average Fine`))))
one_b_1 %>%
  ggplot(aes(x = reorder(`Vehicle Color`, desc(`Average Fine`)), y = `Average Fine`, fill = `Vehicle Color`)) +
  geom_col(alpha = 0.8) +
  scale_fill_brewer(palette = "RdBu", breaks = legend_ord3) +
  
  labs(
  title = "Top 10 highest Average Fine per Car Color",
  x = "",
  y = "Dollar ($)",
  x.axis = "",
  caption = "Source: data.cityofnewyork.us"
  ) +
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect(),
   
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    
    legend.key = element_rect(fill = "white", colour = "white"),
    legend.background = element_rect()
    ) + 
  
  scale_y_continuous(labels = comma)

Vehicle Year

one_b_2 <- df %>%
  dplyr::select(`Vehicle Year`, `Fine Amount $`) %>%
  group_by(`Vehicle Year`) %>%
  summarize(`Average Fine` = mean(`Fine Amount $`)) %>%
  filter(`Vehicle Year` > 0 & `Vehicle Year` <= 2021) %>%
  arrange(desc(`Average Fine`)) %>%
  head(10)
legend_ord4 <- levels(with(one_b_2, reorder(`Vehicle Year`, desc(`Average Fine`))))
one_b_2 %>%
  ggplot(aes(x = reorder(`Vehicle Year`, desc(`Average Fine`)), y = `Average Fine`, fill = as.factor(`Vehicle Year`))) +
  geom_col(alpha = 0.8) +
  scale_fill_brewer(palette = "RdBu", breaks = legend_ord3) +
  
  labs(
  title = "Top 10 highest Average Fine per Vehicle Registration Year",
  x = "Registration Year",
  y = "Dollar ($)",
  x.axis = "",
  caption = "Source: data.cityofnewyork.us"
  ) +
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect(),
    
    legend.key = element_rect(fill = "white", colour = "white"),
    legend.background = element_rect()
    ) + 
  
  scale_y_continuous(labels = comma)

Vehicle Plate Type

one_b_3 <- df %>%
  dplyr::select(`Plate Type`, `Fine Amount $`) %>%
  group_by(`Plate Type`) %>%
  filter(`Plate Type` == "COM" | `Plate Type` == "PAS") %>%
  summarize(`Average Fine` = mean(`Fine Amount $`, na.rm=TRUE)) %>%
  arrange(desc(`Average Fine`))
one_b_3 %>%
  ggplot(aes(x = reorder(`Plate Type`, desc(`Average Fine`)), y = `Average Fine`, fill = `Plate Type`)) +
  geom_col(alpha = 0.8) +
  scale_fill_brewer(palette = "RdYlBu") +
  
  geom_hline(yintercept=89.704) +
  
  labs(
  title = "Average Fine per Car Type",
  x = "",
  y = "Dollar ($)",
  x.axis = "",
  caption = "Source: data.cityofnewyork.us"
  ) +
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect(),
   
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    
    legend.key = element_rect(fill = "white", colour = "white"),
    legend.background = element_rect()
    ) + 
  
  scale_y_continuous(labels = comma, limits = c(0, 100))

c) Effect of COVID

Let’s see if we can observe the effect of COVID restrictions on parking violations. Present a visualization that shows how parking violations changed after the New York statewide stay-at-home order on March 14, 2020. Make sure the visualization clearly highlights the main pattern (the COVID effect).

one_c <- df %>%
  filter(`year` == 2020) %>%
  dplyr::select(`month`, `Violation Description`) %>%
  add_count(`Violation Description`) %>%
  group_by(`month`) %>%
  summarize(Mean = mean(n))
one_c %>%

  ggplot(aes(x = `month`, y = Mean)) +
  
  geom_line() +
  geom_vline(xintercept=3.5) +
  xlim(c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")) +
  ylim(50000,200000) +
  
  geom_label(
  label="New York statewide\nstay-at-home order", 
  x=2.2,
  y=145000,
  label.padding = unit(0.55, "lines"),
  label.size = 0.15,
  color = "black",
  fill="lightcyan"
  ) +
  
  labs(
    title = "NY Violations in 2020",
    x = "2020 Months", 
    y = "Average Violations per Month",
    caption = "Source: data.cityofnewyork.us"
    ) + 
  
  
  theme_tufte() + 
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.caption = element_text(hjust = 0, face = "italic"),
    plot.background = element_rect()
    ) 

I tried my best (!), but I can’t really say there has been a “COVID effect”. Violations appear to be lower in the first months of 2020 compared to the summer. Probably snow and hard weather have an harder impact on traffic than Covid restrictions.


2. Map by Precincts

Read in the shape files for the police precincts and remove all precincts outside of Manhattan.

setwd("/Users/davidev7/Documents/Columbia/Second Semester/Data Visualization/course_content-main/Exercises/07_parking-graded/data")
file.exists('/Users/davidev7/Documents/Columbia/Second Semester/Data Visualization/course_content-main/Exercises/07_parking-graded/data/nypp_21a')

nypp <- readOGR(dsn = '/Users/davidev7/Documents/Columbia/Second Semester/Data Visualization/course_content-main/Exercises/07_parking-graded/data/nypp_21a', layer = "nypp")
str(nypp,max.level = 2)
Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
  ..@ data       :'data.frame': 77 obs. of  3 variables:
  ..@ polygons   :List of 77
  ..@ plotOrder  : int [1:77] 75 77 76 71 63 67 28 69 74 38 ...
  ..@ bbox       : num [1:2, 1:2] 913175 120122 1067383 272844
  .. ..- attr(*, "dimnames")=List of 2
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
tm_shape(nypp) +
  tm_fill() +
  tm_borders() 

I know from NYC website that Manhattan precincts are 1 to 34.

mnh<- subset(nypp, nypp$Precinct<=34)

tm_shape(mnh) +
  tm_fill() +
  tm_borders() 

a) Number of tickets, total fines, and average fines

Provide three maps that show choropleth maps of:

  • the total number of tickets
  • the total amount of fines
  • the average amount of fines

Briefly describe what you learn from these maps in comparison.

mnh_df <- df %>%
  filter(`Violation Precinct` > 0 & `Violation Precinct` <= 34 )

Now I need to merge the map with the violation’s dataset. However, since the resulting object would be too big, I’ll make little datasets related to the question and merge those one by one.

mnh
class       : SpatialPolygonsDataFrame 
features    : 22 
extent      : 971013.5, 1009023, 188082.3, 259027.5  (xmin, xmax, ymin, ymax)
crs         : +proj=lcc +lat_0=40.1666666666667 +lon_0=-74 +lat_1=41.0333333333333 +lat_2=40.6666666666667 +x_0=300000 +y_0=0 +datum=NAD83 +units=us-ft +no_defs 
variables   : 3
names       : Precinct,    Shape_Leng,    Shape_Area 
min values  :        1, 17098.9870878,  15289544.596 
max values  :       34, 80969.4264974, 52113460.9163 
mnh@data[["Precinct"]]
 [1]  1  5  6  7  9 10 13 14 17 18 19 20 22 23 24 25 26 28 30 32 33 34
sort((unique(mnh_df$`Violation Precinct`)))
 [1]  1  2  3  4  5  6  7  8  9 10 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 30 32 33 34
  • the total number of tickets
tickets <- mnh_df %>%
  group_by(`Violation Precinct`) %>%
  count() %>%
  rename(`Total Number of Tickets` = "n")
tickets_map <- merge(mnh, tickets, by.x = "Precinct", by.y = "Violation Precinct", all.x = TRUE, duplicateGeoms = TRUE)
tm_shape(tickets_map) +
  tm_fill(col = "Total Number of Tickets") +
  tm_borders() 

  • the total amount of fines
total_fines <- mnh_df %>%
  dplyr::select(`Violation Precinct`, `Fine Amount $`) %>%
  group_by(`Violation Precinct`) %>% 
  summarize(`Total Fines Amount $` = sum(`Fine Amount $`, na.rm = TRUE))
fines_map <- tickets_map <- merge(mnh, total_fines, by.x = "Precinct", by.y = "Violation Precinct", all.x = TRUE, duplicateGeoms = TRUE)
tm_shape(fines_map) +
  tm_fill(col = "Total Fines Amount $") +
  tm_borders() 

  • the average amount of fines
average_fines <- mnh_df %>%
  dplyr::select(`Violation Precinct`, `Fine Amount $`) %>%
  group_by(`Violation Precinct`) %>% 
  summarize(`Mean Fines Amount $` = mean(`Fine Amount $`, na.rm = TRUE))
mnh_avg_fines <- merge(mnh, average_fines, by.x = "Precinct", by.y = "Violation Precinct", all.x = TRUE, duplicateGeoms = TRUE)
tm_shape(mnh_avg_fines) +
  tm_fill(col = "Mean Fines Amount $") +
  tm_borders() 

b) Types of violations

Group the almost 100 types of ticket violations into a smaller set of 4-6 subgroups (where other should be the remainder of violations not included in other groups you defined). [Hint: No need to spend more than 5 minutes thinking about what the right grouping is.]. Provide choropleth maps for each of these subgroups to show where different types of violations are more or less common.

parking_list = c(9:11, 13:28, 30:33, 37:39, 44, 46, 47, 55, 59, 60, 62:65, 77)
sticker_list = c(68:76)
m2b <- mnh_df %>%
  dplyr::select(`Violation Precinct`, `Violation Description`, `Violation Code`) %>%
  mutate(
    Type = case_when(
      `Violation Code` == 1 | `Violation Code` == 4 | `Violation Code` == 5 | `Violation Code` == 12 | `Violation Code` == 18 | `Violation Code` == 19 ~ "BUS related violations",
      `Violation Code` == 67 |`Violation Code`== 50 | `Violation Code` == 51 | `Violation Code` == 48 ~ "Violations against pedestrian traffic",
      `Violation Code` %in% parking_list ~ "Parking violations",
      `Violation Code` %in% sticker_list ~ "Sticker violations",
      TRUE ~ "Other"
    )
  )
other <- m2b %>%
  filter(`Type` == "Other") %>%
  group_by(`Violation Precinct`) %>%
  count() %>%
  rename(`Other Violations` = "n")
pedestrians <- m2b %>%
  filter(`Type` == "Violations against pedestrian traffic") %>%
  group_by(`Violation Precinct`) %>%
  count() %>%
  rename(`Violations vs Pedestrians` = "n")
parking <- m2b %>%
  filter(`Type` == "Parking violations") %>%
  group_by(`Violation Precinct`) %>%
  count() %>%
  rename(`Parking Violations` = "n")
sticker <- m2b %>%
  filter(`Type` == "Sticker violations") %>%
  group_by(`Violation Precinct`) %>%
  count() %>%
  rename(`Sticker Violations` = "n")
mnh_map <- merge(mnh_map, parking, by.x = "Precinct", by.y = "Violation Precinct", all.x = TRUE, duplicateGeoms = TRUE)
Error in h(simpleError(msg, call)) : 
  error in evaluating the argument 'x' in selecting a method for function 'merge': oggetto "mnh_map" non trovato
tm_other <- tm_shape(mnh_map) +
  tm_fill(col = "Other Violations") +
  tm_borders() 
tm_pede <- tm_shape(mnh_map) +
  tm_fill(col = "Violations vs Pedestrians") +
  tm_borders() 
tm_parking <- tm_shape(mnh_map) +
  tm_fill(col = "Parking Violations") +
  tm_borders() 
tm_sticker <- tm_shape(mnh_map) +
  tm_fill(col = "Sticker Violations") +
  tm_borders() 
tmap_arrange(tm_sticker, tm_parking, tm_pede, tm_other)


3. Focus on the Upper East

Precinct 19 identifies the Upper East Side. The data currently does not provide latitude and longitude of the violation locations (and I am not sure what these street_code variables are for).

a) Ignoring fire hydrants

Restrict your data to parking violations related to fire hydrants (Violation Code = 40). Using the variables Street Name and House Number as well as the knowledge that these addresses are in the Upper East Side of Manhattan, geocode at least 500 addresses. Include a data table of these addresses and the latitude and longitude of these addresses in the output.

geoloc_df <- mnh_df %>%
  filter(`Violation Precinct` == 19 & `Violation Code` == 40) %>%
  dplyr::select(`Street Name`, `House Number`, `issue_date`, `Violation Description`, `Fine Amount $`, `Plate ID`, `Vehicle Make`)
geoloc_df$`House Number` <- as.numeric(geoloc_df$`House Number`)
geoloc_df <- drop_na(geoloc_df)

geoloc_df <- geoloc_df %>%
  unite(`Street Name`, `House Number`, col = "Location", sep = ", ", remove = FALSE)

geoloc_df$Location <- paste("United States, NY, New York,", geoloc_df$Location, sep=" ")

head(geoloc_df$Location)

geo_df <- geoloc_df[sample(nrow(geoloc_df), 700), ]
example <- base::sample(x = geo_df$Location, 
                               size = 1, 
                               replace = TRUE)
example
ggmap::geocode(location = example)
geocoded_df <- dplyr::bind_cols(geo_df$Location, GeoCoded) %>% 
  dplyr::select(
    lng = lon,
    lat,
    dplyr::everything())
New names:
* NA -> ...1
rename(geocoded_df, "Address" = `...1`)
datatable(geocoded_df,
          filter = 'top',
          options = list(pageLength = 5, autoWidth = TRUE),
          colnames = c('lng', 'lat', 'Address'),
          )
b) Interactive Map

Provide an interactive map of the violations you geocoded using leaflet. Provide at least three pieces of information on the parking ticket in a popup.

library(leaflet)
geo_df <- dplyr::bind_cols(geo_df, GeoCoded) %>% 
  dplyr::select(
    lng = lon,
    lat,
    dplyr::everything())
# creating a popuptext column
geo_df <- geo_df %>%  
  dplyr::mutate(popuptext = base::paste0("<b>", 
                                 str_to_title(geo_df$`Violation Description`), " Violation",
                                 "</b><br />",
                                 "<i>", "Date: ",
                                 geo_df$issue_date, 
                                 ", ", "Fine Amount: $",
                                 geo_df$`Fine Amount $`,
                                 "</i><br />",
                                 "<i>"))
leaflet(geo_df) %>%
  addTiles() %>%
  addCircles(lng = ~lng, lat = ~lat, color = "orange", popup = ~popuptext) %>%
  setView(lng = -73.956189, lat = 40.774917, zoom = 14) %>%
  addProviderTiles(providers$CartoDB.DarkMatter)
c) Luxury cars and repeat offenders

Using the vehicle Plate ID, identify repeat offenders (in the full dataset). Create another variable called luxury_car in which you identify luxury car brands using the Vehicle Make variable.

Start with the previous map. Distinguish the points by whether the car is a repeat offender and/or luxury car. Add a legend informing the user about the color scheme. Also make sure that the added information about the car type and repeat offender status is now contained in the popup information. Show this map.

gino <- mnh_df %>%
  dplyr::select(`Plate ID`) %>%
  group_by(`Plate ID`) %>%
  add_count(`Plate ID`) %>%
  unique() %>%
  mutate(`Repeat Offender` = ifelse(n > 1, paste("Yes"), paste("No")))
gino %>%
  arrange(desc(n)) %>%
  head(10)

Wow! Besides “BLANKPLATE”, some of these people could have gone broke for all these fines!

sort(unique(mnh_df$`Vehicle Make`))
luxury_cars = c("MASE", "FERRA", "FER", "ASTO", "ROLLS", "MAYBA", "BUGAT", "BENT", "TESLA", "TELSA", "TSLA", "PORSC", "ROLLI", "AUDI", "BMW", "LAMBO", "MERC")
geo_df <- geo_df %>%
  mutate(
    `Car Type` = case_when(
    `Vehicle Make` %in% luxury_cars ~ "Luxury car",
      TRUE ~ "Non-luxury car"
  ))
geo_df <- left_join(geo_df, gino, by = "Plate ID", all.x = TRUE)
library(RColorBrewer)
pal = colorFactor("Set1", domain = geo_df$`Repeat Offender`)
rpof_color = pal(geo_df$`Repeat Offender`)
popup <- paste("<b>", "Plate ID: ", "</b>", geo_df$`Plate ID`, "<br/>",
               "<b>","Car Type: ", "</b>", geo_df$`Car Type`, "<br/>",
               "<b>","Repeat Offender: ", "</b>", geo_df$`Repeat Offender`, "<br/>")
leaflet(geo_df) %>%
  addTiles() %>%
  addCircles(lng = ~lng, lat = ~lat, color = rpof_color, popup = ~popup) %>%
  addLegend(pal = pal, values = ~geo_df$`Repeat Offender`, title = "Repeat Offender") %>%
  setView(lng = -73.956189, lat = 40.774917, zoom = 14) %>%
  addProviderTiles(providers$CartoDB.DarkMatter)
d) Cluster

Add marker clustering, so that zooming in will reveal the individual locations but the zoomed out map only shows the clusters. Show the map with clusters.

leaflet(geo_df) %>%
  addTiles() %>%
  addCircleMarkers(color = rpof_color, popup = popup, clusterOptions = markerClusterOptions()) %>%
  addLegend(pal = pal, values = ~geo_df$`Repeat Offender`, title = "Repeat Offender") %>%
  setView(lng = -73.956189, lat = 40.774917, zoom = 14) %>%
  addProviderTiles(providers$CartoDB.DarkMatter)
Assuming "lng" and "lat" are longitude and latitude, respectively
LS0tCnRpdGxlOiAnQXNzaWdubWVudCAyOiBNYXBwaW5nIFBhcmtpbmcgVmlvbGF0aW9ucyBpbiBOWUMnCmF1dGhvcjogIkRhdmlkZSBWYWNjYXJpIgpkYXRlOiAiMy8xNy8yMDIxIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBlcnJvciA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKa25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSBub3JtYWxpemVQYXRoKCIvVXNlcnMvZGF2aWRldjcvRG9jdW1lbnRzL0NvbHVtYmlhL1NlY29uZCBTZW1lc3Rlci9EYXRhIFZpc3VhbGl6YXRpb24vY291cnNlX2NvbnRlbnQtbWFpbi9FeGVyY2lzZXMvMDdfcGFya2luZy1ncmFkZWQvZGF0YS9wYXJraW5nIikpCmBgYAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkodG1hcCkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoc2YpCmxpYnJhcnkocmFzdGVyKQpsaWJyYXJ5KHJnZW9zKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KGRldnRvb2xzKQpgYGAKCmBgYHtyfQpnZ21hcDo6cmVnaXN0ZXJfZ29vZ2xlKGtleSA9ICJBSXphU3lCMS1NamlYRUlyZ2RUM0ZiZmxNTGM4RVVhUVhWRzNYVlkiKQpgYGAKCmBgYHtyfQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInJzdHVkaW8vbGVhZmxldCIpCmBgYAoKUGFya2luZyBWaW9sYXRpb25zIGluIE5ZQwo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQpkZiA8LSByZWFkX2NzdigicGFya2luZ05ZQ19KYW4yMDIwLUphbjIwMjEuY3N2IikKYGBgCmBgYHtyfQpmaW5lcyA8LSByZWFkX2V4Y2VsKCJQYXJraW5nVmlvbGF0aW9uQ29kZXNfSmFudWFyeTIwMjAueGxzeCIpCmBgYAoKIyMjIyMgMS4gRGF0YSBleHBsb3JhdGlvbgoKYGBge3J9CmhlYWQoZGYpCmNvbG5hbWVzKGRmKQpkZiA8LSBkcGx5cjo6c2VsZWN0KGRmLCAtIlZpb2xhdGlvbiBEZXNjcmlwdGlvbiIpCmBgYApgYGB7cn0KY29sbmFtZXMoZmluZXMpIDwtIHN0cl90b190aXRsZShjb2xuYW1lcyhmaW5lcykpCmZpbmVzIDwtIGZpbmVzW2MoMTozKV0KY29sbmFtZXMoZmluZXMpWzNdIDwtICJGaW5lIEFtb3VudCAkIgpoZWFkKGZpbmVzKQpgYGAKClZpb2xhdGlvbiAzOCBbRkFJTCBUTyBEU1BMWSBNVU5JIE1FVEVSIFJFQ1BUXSBhbmQgNjkgW0ZBSUwgVE8gRElTUC4gTVVOSSBNRVRFUiBSRUNQVF0gbG9vayBsaWtlIHRoZSBzYW1lIGluZnJhY3Rpb24uIFRoZSBmaW5lIGlzICQ2NSBmb3IgYm90aC4gSSdkIHJlbmFtZSBvbmUgYXMgdGhlIG90aGVyIGFuZCBhZ2dyZWdhdGUgdGhlbSBhcyB0aGUgc2FtZSB2aW9sYXRpb24uCgpgYGB7cn0KZmluZXMkYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmBbNjldIDwtICJGQUlMIFRPIERTUExZIE1VTkkgTUVURVIgUkVDUFQiCmBgYAoKYGBge3J9CmRmIDwtIG1lcmdlKHggPSBkZiwgeSA9IGZpbmVzLCBieSA9ICJWaW9sYXRpb24gQ29kZSIsIGFsbC54ID0gVFJVRSkKYGBgCgojIyMjIyBhKSBWaW9sYXRpb24gQ29kZSBhbmQgRmluZSBBbW91bnRzCgpBZGQgdGhlIHZpb2xhdGlvbiBjb2RlIGRlc2NyaXB0aW9ucyBhbmQgZmluZSBhbW91bnRzIHRvIHRoZSBkYXRhIGZpbGUuIFByb3ZpZGUgYSB2aXN1YWwgb3ZlcnZpZXcgb2YgdGhlIHRvcCAxMCBtb3N0IGNvbW1vbiB0eXBlcyBvZiB2aW9sYXRpb25zIChmZWVsIGZyZWUgdG8gZ3JvdXAgdGhlbSBpbnRvIGNhdGVnb3JpZXMgaWYgcmVhc29uYWJsZSkuIENvbXBhcmUgaG93IHRoaXMgcmFua2luZyBkaWZmZXJzIGlmIHdlIGZvY3VzIG9uIHRoZSB0b3RhbCBhbW91bnQgb2YgcmV2ZW51ZSBnZW5lcmF0ZWQuCgoqKlRvcCAxMCBWaW9sYXRpb25zKioKCmBgYHtyfQpvbmVfYSA8LSBkZiAlPiUKICBkcGx5cjo6c2VsZWN0KGBWaW9sYXRpb24gRGVzY3JpcHRpb25gKSAlPiUKICBncm91cF9ieShgVmlvbGF0aW9uIERlc2NyaXB0aW9uYCkgJT4lCiAgY291bnQoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBoZWFkKDEwKQpgYGAKCmBgYHtyfQpsZWdlbmRfb3JkIDwtIGxldmVscyh3aXRoKG9uZV9hLCByZW9yZGVyKGBWaW9sYXRpb24gRGVzY3JpcHRpb25gLCBkZXNjKG4pKSkpCmBgYAoKYGBge3J9Cm9uZV9hICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmAsIGRlc2MobikpLCB5ID0gbiwgZmlsbCA9IGBWaW9sYXRpb24gRGVzY3JpcHRpb25gKSkgKwogIGdlb21fY29sKGFscGhhID0gMC44KSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJSZEJ1IiwgYnJlYWtzID0gbGVnZW5kX29yZCkgKwogIAogIGxhYnMoCiAgdGl0bGUgPSAiVG9wIDEwIG1vc3QgY29tbW9uIHR5cGVzIG9mIHZpb2xhdGlvbnMgaW4gTllDIiwKICB4ID0gIiIsCiAgeSA9ICJOdW1iZXIgb2YgVmlvbGF0aW9ucyIsCiAgeC5heGlzID0gIiIsCiAgY2FwdGlvbiA9ICJTb3VyY2U6IGRhdGEuY2l0eW9mbmV3eW9yay51cyIKICApICsKICAKICB0aGVtZV90dWZ0ZSgpICsgCiAgdGhlbWUoCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBmYWNlID0gIml0YWxpYyIpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCksCiAgIAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAKICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIsIGNvbG91ciA9ICJ3aGl0ZSIpLAogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoKQogICAgKSArIAogIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkKYGBgCgoqKlRvcCAxMCBWaW9sYXRpb25zIGZvciByZXZlbnVlKioKCmBgYHtyfQpvbmVfYV8yIDwtIGRmICU+JQogIGRwbHlyOjpzZWxlY3QoYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmAsIGBGaW5lIEFtb3VudCAkYCkgJT4lCiAgZ3JvdXBfYnkoYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmAsYEZpbmUgQW1vdW50ICRgKSAlPiUgCiAgc3VtbWFyaXplKGBUb3RhbCBGaW5lcyBBbW91bnQgJGAgPSBzdW0oYEZpbmUgQW1vdW50ICRgKSkgJT4lCiAgYXJyYW5nZShkZXNjKGBUb3RhbCBGaW5lcyBBbW91bnQgJGApKSAlPiUKICBoZWFkKDEwKQpgYGAKCmBgYHtyfQpsZWdlbmRfb3JkMiA8LSBsZXZlbHMod2l0aChvbmVfYV8yLCByZW9yZGVyKGBWaW9sYXRpb24gRGVzY3JpcHRpb25gLCBkZXNjKGBUb3RhbCBGaW5lcyBBbW91bnQgJGApKSkpCmBgYAoKYGBge3J9Cm9uZV9hXzIgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihgVmlvbGF0aW9uIERlc2NyaXB0aW9uYCwgZGVzYyhgVG90YWwgRmluZXMgQW1vdW50ICRgKSksIHkgPSBgVG90YWwgRmluZXMgQW1vdW50ICRgLCBmaWxsID0gYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmApKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlJkQnUiLCBicmVha3MgPSBsZWdlbmRfb3JkMikgKwogIAogIGxhYnMoCiAgdGl0bGUgPSAiVG9wIDEwIGhpZ2hlc3QgdmlvbGF0aW9ucyBpbiBOWUMgcGVyIHJldmVudWUiLAogIHggPSAiIiwKICB5ID0gIkRvbGxhciAoJCkiLAogIHguYXhpcyA9ICIiLAogIGNhcHRpb24gPSAiU291cmNlOiBkYXRhLmNpdHlvZm5ld3lvcmsudXMiCiAgKSArCiAgCiAgdGhlbWVfdHVmdGUoKSArIAogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCwgZmFjZSA9ICJpdGFsaWMiKSwKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgpLAogICAKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvdXIgPSAid2hpdGUiKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCkKICAgICkgKyAKICAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpCmBgYAoKCiMjIyMjIGIpIEF2ZXJhZ2UgYW1vdW50IG9mIGZpbmUgYnkgdmVoaWNsZQoKQ29tcGFyZSB0aGUgYXZlcmFnZSBhbW91bnQgb2YgZmluZSBieSB2ZWhpY2xlIGNvbG9yLCB2ZWhpY2xlIHllYXIsIGFuZCBbdmVoaWNsZSBwbGF0ZSB0eXBlXShodHRwczovL2Rtdi5ueS5nb3YvcmVnaXN0cmF0aW9uL3JlZ2lzdHJhdGlvbi1jbGFzcy1jb2RlcykgW0hpbnQ6IGl0IGlzIHN1ZmZpY2llbnQgdG8gcmVzdHJpY3QgeW91ciBhdHRlbnRpb24gdG8gY29tbWVyY2lhbCAoYENPTWApIGFuZCBwYXNzZW5nZXIgKGBQQVNgKSB2ZWhpY2xlc10/IEJyaWVmbHkgZGVzY3JpYmUgeW91ciBmaW5kaW5ncy4KCioqVmVoaWNsZSBDb2xvcioqCgpgYGB7cn0Kb25lX2JfMSA8LSBkZiAlPiUKICBkcGx5cjo6c2VsZWN0KGBWZWhpY2xlIENvbG9yYCwgYEZpbmUgQW1vdW50ICRgKSAlPiUKICBkcm9wX25hKGBGaW5lIEFtb3VudCAkYCkgJT4lCiAgZ3JvdXBfYnkoYFZlaGljbGUgQ29sb3JgKSAlPiUKICBhZGRfY291bnQoYFZlaGljbGUgQ29sb3JgKSAlPiUKICBmaWx0ZXIobiA+IDEwKSAlPiUKICBzdW1tYXJpemUoYEF2ZXJhZ2UgRmluZWAgPSBtZWFuKGBGaW5lIEFtb3VudCAkYCkpICU+JQogIGFycmFuZ2UoZGVzYyhgQXZlcmFnZSBGaW5lYCkpICU+JQogIGhlYWQoMTApCmBgYAoKYGBge3J9CmxlZ2VuZF9vcmQzIDwtIGxldmVscyh3aXRoKG9uZV9iXzEsIHJlb3JkZXIoYFZlaGljbGUgQ29sb3JgLCBkZXNjKGBBdmVyYWdlIEZpbmVgKSkpKQpgYGAKCmBgYHtyfQpvbmVfYl8xICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoYFZlaGljbGUgQ29sb3JgLCBkZXNjKGBBdmVyYWdlIEZpbmVgKSksIHkgPSBgQXZlcmFnZSBGaW5lYCwgZmlsbCA9IGBWZWhpY2xlIENvbG9yYCkpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUmRCdSIsIGJyZWFrcyA9IGxlZ2VuZF9vcmQzKSArCiAgCiAgbGFicygKICB0aXRsZSA9ICJUb3AgMTAgaGlnaGVzdCBBdmVyYWdlIEZpbmUgcGVyIENhciBDb2xvciIsCiAgeCA9ICIiLAogIHkgPSAiRG9sbGFyICgkKSIsCiAgeC5heGlzID0gIiIsCiAgY2FwdGlvbiA9ICJTb3VyY2U6IGRhdGEuY2l0eW9mbmV3eW9yay51cyIKICApICsKICAKICB0aGVtZV90dWZ0ZSgpICsgCiAgdGhlbWUoCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBmYWNlID0gIml0YWxpYyIpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCksCiAgIAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAKICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIsIGNvbG91ciA9ICJ3aGl0ZSIpLAogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoKQogICAgKSArIAogIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkKYGBgCgoKKipWZWhpY2xlIFllYXIqKgoKYGBge3J9Cm9uZV9iXzIgPC0gZGYgJT4lCiAgZHBseXI6OnNlbGVjdChgVmVoaWNsZSBZZWFyYCwgYEZpbmUgQW1vdW50ICRgKSAlPiUKICBncm91cF9ieShgVmVoaWNsZSBZZWFyYCkgJT4lCiAgc3VtbWFyaXplKGBBdmVyYWdlIEZpbmVgID0gbWVhbihgRmluZSBBbW91bnQgJGApKSAlPiUKICBmaWx0ZXIoYFZlaGljbGUgWWVhcmAgPiAwICYgYFZlaGljbGUgWWVhcmAgPD0gMjAyMSkgJT4lCiAgYXJyYW5nZShkZXNjKGBBdmVyYWdlIEZpbmVgKSkgJT4lCiAgaGVhZCgxMCkKYGBgCgpgYGB7cn0KbGVnZW5kX29yZDQgPC0gbGV2ZWxzKHdpdGgob25lX2JfMiwgcmVvcmRlcihgVmVoaWNsZSBZZWFyYCwgZGVzYyhgQXZlcmFnZSBGaW5lYCkpKSkKYGBgCgpgYGB7cn0Kb25lX2JfMiAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGBWZWhpY2xlIFllYXJgLCBkZXNjKGBBdmVyYWdlIEZpbmVgKSksIHkgPSBgQXZlcmFnZSBGaW5lYCwgZmlsbCA9IGFzLmZhY3RvcihgVmVoaWNsZSBZZWFyYCkpKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlJkQnUiLCBicmVha3MgPSBsZWdlbmRfb3JkMykgKwogIAogIGxhYnMoCiAgdGl0bGUgPSAiVG9wIDEwIGhpZ2hlc3QgQXZlcmFnZSBGaW5lIHBlciBWZWhpY2xlIFJlZ2lzdHJhdGlvbiBZZWFyIiwKICB4ID0gIlJlZ2lzdHJhdGlvbiBZZWFyIiwKICB5ID0gIkRvbGxhciAoJCkiLAogIHguYXhpcyA9ICIiLAogIGNhcHRpb24gPSAiU291cmNlOiBkYXRhLmNpdHlvZm5ld3lvcmsudXMiCiAgKSArCiAgCiAgdGhlbWVfdHVmdGUoKSArIAogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCwgZmFjZSA9ICJpdGFsaWMiKSwKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgpLAogICAgCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvdXIgPSAid2hpdGUiKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCkKICAgICkgKyAKICAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpCmBgYAoKCioqVmVoaWNsZSBQbGF0ZSBUeXBlKioKCmBgYHtyfQpvbmVfYl8zIDwtIGRmICU+JQogIGRwbHlyOjpzZWxlY3QoYFBsYXRlIFR5cGVgLCBgRmluZSBBbW91bnQgJGApICU+JQogIGdyb3VwX2J5KGBQbGF0ZSBUeXBlYCkgJT4lCiAgZmlsdGVyKGBQbGF0ZSBUeXBlYCA9PSAiQ09NIiB8IGBQbGF0ZSBUeXBlYCA9PSAiUEFTIikgJT4lCiAgc3VtbWFyaXplKGBBdmVyYWdlIEZpbmVgID0gbWVhbihgRmluZSBBbW91bnQgJGAsIG5hLnJtPVRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoYEF2ZXJhZ2UgRmluZWApKQpgYGAKCmBgYHtyfQpvbmVfYl8zICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoYFBsYXRlIFR5cGVgLCBkZXNjKGBBdmVyYWdlIEZpbmVgKSksIHkgPSBgQXZlcmFnZSBGaW5lYCwgZmlsbCA9IGBQbGF0ZSBUeXBlYCkpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuOCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUmRZbEJ1IikgKwogIAogIGdlb21faGxpbmUoeWludGVyY2VwdD04OS43MDQpICsKICAKICBsYWJzKAogIHRpdGxlID0gIkF2ZXJhZ2UgRmluZSBwZXIgQ2FyIFR5cGUiLAogIHggPSAiIiwKICB5ID0gIkRvbGxhciAoJCkiLAogIHguYXhpcyA9ICIiLAogIGNhcHRpb24gPSAiU291cmNlOiBkYXRhLmNpdHlvZm5ld3lvcmsudXMiCiAgKSArCiAgCiAgdGhlbWVfdHVmdGUoKSArIAogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCwgZmFjZSA9ICJpdGFsaWMiKSwKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgpLAogICAKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvdXIgPSAid2hpdGUiKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCkKICAgICkgKyAKICAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEsIGxpbWl0cyA9IGMoMCwgMTAwKSkKYGBgCgoKIyMjIyMgYykgRWZmZWN0IG9mIENPVklECgpMZXQncyBzZWUgaWYgd2UgY2FuIG9ic2VydmUgdGhlIGVmZmVjdCBvZiBDT1ZJRCByZXN0cmljdGlvbnMgb24gcGFya2luZyB2aW9sYXRpb25zLiBQcmVzZW50IGEgdmlzdWFsaXphdGlvbiB0aGF0IHNob3dzIGhvdyBwYXJraW5nIHZpb2xhdGlvbnMgY2hhbmdlZCBhZnRlciB0aGUgTmV3IFlvcmsgc3RhdGV3aWRlIHN0YXktYXQtaG9tZSBvcmRlciBvbiBNYXJjaCAxNCwgMjAyMC4gTWFrZSBzdXJlIHRoZSB2aXN1YWxpemF0aW9uIGNsZWFybHkgaGlnaGxpZ2h0cyB0aGUgbWFpbiBwYXR0ZXJuICh0aGUgQ09WSUQgZWZmZWN0KS4KCgpgYGB7cn0Kb25lX2MgPC0gZGYgJT4lCiAgZmlsdGVyKGB5ZWFyYCA9PSAyMDIwKSAlPiUKICBkcGx5cjo6c2VsZWN0KGBtb250aGAsIGBWaW9sYXRpb24gRGVzY3JpcHRpb25gKSAlPiUKICBhZGRfY291bnQoYFZpb2xhdGlvbiBEZXNjcmlwdGlvbmApICU+JQogIGdyb3VwX2J5KGBtb250aGApICU+JQogIHN1bW1hcml6ZShNZWFuID0gbWVhbihuKSkKYGBgCgpgYGB7cn0Kb25lX2MgJT4lCgogIGdncGxvdChhZXMoeCA9IGBtb250aGAsIHkgPSBNZWFuKSkgKwogIAogIGdlb21fbGluZSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9My41KSArCiAgeGxpbShjKCJKYW4iLCAiRmViIiwgIk1hciIsICJBcHIiLCAiTWF5IiwgIkp1biIsICJKdWwiLCAiQXVnIiwgIlNlcCIsICJPY3QiLCAiTm92IiwgIkRlYyIpKSArCiAgeWxpbSg1MDAwMCwyMDAwMDApICsKICAKICBnZW9tX2xhYmVsKAogIGxhYmVsPSJOZXcgWW9yayBzdGF0ZXdpZGVcbnN0YXktYXQtaG9tZSBvcmRlciIsIAogIHg9Mi4yLAogIHk9MTQ1MDAwLAogIGxhYmVsLnBhZGRpbmcgPSB1bml0KDAuNTUsICJsaW5lcyIpLAogIGxhYmVsLnNpemUgPSAwLjE1LAogIGNvbG9yID0gImJsYWNrIiwKICBmaWxsPSJsaWdodGN5YW4iCiAgKSArCiAgCiAgbGFicygKICAgIHRpdGxlID0gIk5ZIFZpb2xhdGlvbnMgaW4gMjAyMCIsCiAgICB4ID0gIjIwMjAgTW9udGhzIiwgCiAgICB5ID0gIkF2ZXJhZ2UgVmlvbGF0aW9ucyBwZXIgTW9udGgiLAogICAgY2FwdGlvbiA9ICJTb3VyY2U6IGRhdGEuY2l0eW9mbmV3eW9yay51cyIKICAgICkgKyAKICAKICAKICB0aGVtZV90dWZ0ZSgpICsgCiAgdGhlbWUoCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBmYWNlID0gIml0YWxpYyIpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KCkKICAgICkgCmBgYAoKSSB0cmllZCBteSBiZXN0ICghKSwgYnV0IEkgY2FuJ3QgcmVhbGx5IHNheSB0aGVyZSBoYXMgYmVlbiBhICJDT1ZJRCBlZmZlY3QiLiBWaW9sYXRpb25zIGFwcGVhciB0byBiZSBsb3dlciBpbiB0aGUgZmlyc3QgbW9udGhzIG9mIDIwMjAgY29tcGFyZWQgdG8gdGhlIHN1bW1lci4gUHJvYmFibHkgc25vdyBhbmQgaGFyZCB3ZWF0aGVyIGhhdmUgYW4gaGFyZGVyIGltcGFjdCBvbiB0cmFmZmljIHRoYW4gQ292aWQgcmVzdHJpY3Rpb25zLgoKX19fCgojIyMjIDIuIE1hcCBieSBQcmVjaW5jdHMKClJlYWQgaW4gdGhlIHNoYXBlIGZpbGVzIGZvciB0aGUgcG9saWNlIHByZWNpbmN0cyBhbmQgcmVtb3ZlIGFsbCBwcmVjaW5jdHMgb3V0c2lkZSBvZiBNYW5oYXR0YW4uCgpgYGB7cn0Kc2V0d2QoIi9Vc2Vycy9kYXZpZGV2Ny9Eb2N1bWVudHMvQ29sdW1iaWEvU2Vjb25kIFNlbWVzdGVyL0RhdGEgVmlzdWFsaXphdGlvbi9jb3Vyc2VfY29udGVudC1tYWluL0V4ZXJjaXNlcy8wN19wYXJraW5nLWdyYWRlZC9kYXRhIikKZmlsZS5leGlzdHMoJy9Vc2Vycy9kYXZpZGV2Ny9Eb2N1bWVudHMvQ29sdW1iaWEvU2Vjb25kIFNlbWVzdGVyL0RhdGEgVmlzdWFsaXphdGlvbi9jb3Vyc2VfY29udGVudC1tYWluL0V4ZXJjaXNlcy8wN19wYXJraW5nLWdyYWRlZC9kYXRhL255cHBfMjFhJykKCm55cHAgPC0gcmVhZE9HUihkc24gPSAnL1VzZXJzL2RhdmlkZXY3L0RvY3VtZW50cy9Db2x1bWJpYS9TZWNvbmQgU2VtZXN0ZXIvRGF0YSBWaXN1YWxpemF0aW9uL2NvdXJzZV9jb250ZW50LW1haW4vRXhlcmNpc2VzLzA3X3BhcmtpbmctZ3JhZGVkL2RhdGEvbnlwcF8yMWEnLCBsYXllciA9ICJueXBwIikKYGBgCmBgYHtyfQpzdHIobnlwcCxtYXgubGV2ZWwgPSAyKQpgYGAKCmBgYHtyfQp0bV9zaGFwZShueXBwKSArCiAgdG1fZmlsbCgpICsKICB0bV9ib3JkZXJzKCkgCmBgYAoKSSBrbm93IGZyb20gW05ZQyB3ZWJzaXRlXShodHRwczovL3d3dzEubnljLmdvdi9zaXRlL255cGQvYnVyZWF1cy9wYXRyb2wvcHJlY2luY3RzLWxhbmRpbmcucGFnZSkgdGhhdCBNYW5oYXR0YW4gcHJlY2luY3RzIGFyZSAxIHRvIDM0LgoKYGBge3J9Cm1uaDwtIHN1YnNldChueXBwLCBueXBwJFByZWNpbmN0PD0zNCkKCnRtX3NoYXBlKG1uaCkgKwogIHRtX2ZpbGwoKSArCiAgdG1fYm9yZGVycygpIApgYGAKCiMjIyMjIGEpIE51bWJlciBvZiB0aWNrZXRzLCB0b3RhbCBmaW5lcywgYW5kIGF2ZXJhZ2UgZmluZXMKClByb3ZpZGUgdGhyZWUgbWFwcyB0aGF0IHNob3cgY2hvcm9wbGV0aCBtYXBzIG9mOgoKICAtIHRoZSB0b3RhbCBudW1iZXIgb2YgdGlja2V0cyAKICAtIHRoZSB0b3RhbCBhbW91bnQgb2YgZmluZXMgCiAgLSB0aGUgYXZlcmFnZSBhbW91bnQgb2YgZmluZXMKICAKQnJpZWZseSBkZXNjcmliZSB3aGF0IHlvdSBsZWFybiBmcm9tIHRoZXNlIG1hcHMgaW4gY29tcGFyaXNvbi4KCmBgYHtyfQptbmhfZGYgPC0gZGYgJT4lCiAgZmlsdGVyKGBWaW9sYXRpb24gUHJlY2luY3RgID4gMCAmIGBWaW9sYXRpb24gUHJlY2luY3RgIDw9IDM0ICkKYGBgCgpOb3cgSSBuZWVkIHRvIG1lcmdlIHRoZSBtYXAgd2l0aCB0aGUgdmlvbGF0aW9uJ3MgZGF0YXNldC4gSG93ZXZlciwgc2luY2UgdGhlIHJlc3VsdGluZyBvYmplY3Qgd291bGQgYmUgdG9vIGJpZywgSSdsbCBtYWtlIGxpdHRsZSBkYXRhc2V0cyByZWxhdGVkIHRvIHRoZSBxdWVzdGlvbiBhbmQgbWVyZ2UgdGhvc2Ugb25lIGJ5IG9uZS4KCmBgYHtyfQptbmgKbW5oQGRhdGFbWyJQcmVjaW5jdCJdXQpzb3J0KCh1bmlxdWUobW5oX2RmJGBWaW9sYXRpb24gUHJlY2luY3RgKSkpCmBgYAoKLSB0aGUgdG90YWwgbnVtYmVyIG9mIHRpY2tldHMgCiAKYGBge3J9CnRpY2tldHMgPC0gbW5oX2RmICU+JQogIGdyb3VwX2J5KGBWaW9sYXRpb24gUHJlY2luY3RgKSAlPiUKICBjb3VudCgpICU+JQogIHJlbmFtZShgVG90YWwgTnVtYmVyIG9mIFRpY2tldHNgID0gIm4iKQpgYGAKCmBgYHtyfQp0aWNrZXRzX21hcCA8LSBtZXJnZShtbmgsIHRpY2tldHMsIGJ5LnggPSAiUHJlY2luY3QiLCBieS55ID0gIlZpb2xhdGlvbiBQcmVjaW5jdCIsIGFsbC54ID0gVFJVRSwgZHVwbGljYXRlR2VvbXMgPSBUUlVFKQpgYGAKCmBgYHtyfQp0bV9zaGFwZSh0aWNrZXRzX21hcCkgKwogIHRtX2ZpbGwoY29sID0gIlRvdGFsIE51bWJlciBvZiBUaWNrZXRzIikgKwogIHRtX2JvcmRlcnMoKSAKYGBgCgotIHRoZSB0b3RhbCBhbW91bnQgb2YgZmluZXMKCmBgYHtyfQp0b3RhbF9maW5lcyA8LSBtbmhfZGYgJT4lCiAgZHBseXI6OnNlbGVjdChgVmlvbGF0aW9uIFByZWNpbmN0YCwgYEZpbmUgQW1vdW50ICRgKSAlPiUKICBncm91cF9ieShgVmlvbGF0aW9uIFByZWNpbmN0YCkgJT4lIAogIHN1bW1hcml6ZShgVG90YWwgRmluZXMgQW1vdW50ICRgID0gc3VtKGBGaW5lIEFtb3VudCAkYCwgbmEucm0gPSBUUlVFKSkKYGBgCgpgYGB7cn0KZmluZXNfbWFwIDwtIHRpY2tldHNfbWFwIDwtIG1lcmdlKG1uaCwgdG90YWxfZmluZXMsIGJ5LnggPSAiUHJlY2luY3QiLCBieS55ID0gIlZpb2xhdGlvbiBQcmVjaW5jdCIsIGFsbC54ID0gVFJVRSwgZHVwbGljYXRlR2VvbXMgPSBUUlVFKQpgYGAKCmBgYHtyfQp0bV9zaGFwZShmaW5lc19tYXApICsKICB0bV9maWxsKGNvbCA9ICJUb3RhbCBGaW5lcyBBbW91bnQgJCIpICsKICB0bV9ib3JkZXJzKCkgCmBgYAoKLSB0aGUgYXZlcmFnZSBhbW91bnQgb2YgZmluZXMKCmBgYHtyfQphdmVyYWdlX2ZpbmVzIDwtIG1uaF9kZiAlPiUKICBkcGx5cjo6c2VsZWN0KGBWaW9sYXRpb24gUHJlY2luY3RgLCBgRmluZSBBbW91bnQgJGApICU+JQogIGdyb3VwX2J5KGBWaW9sYXRpb24gUHJlY2luY3RgKSAlPiUgCiAgc3VtbWFyaXplKGBNZWFuIEZpbmVzIEFtb3VudCAkYCA9IG1lYW4oYEZpbmUgQW1vdW50ICRgLCBuYS5ybSA9IFRSVUUpKQpgYGAKCmBgYHtyfQptbmhfYXZnX2ZpbmVzIDwtIG1lcmdlKG1uaCwgYXZlcmFnZV9maW5lcywgYnkueCA9ICJQcmVjaW5jdCIsIGJ5LnkgPSAiVmlvbGF0aW9uIFByZWNpbmN0IiwgYWxsLnggPSBUUlVFLCBkdXBsaWNhdGVHZW9tcyA9IFRSVUUpCmBgYAoKYGBge3J9CnRtX3NoYXBlKG1uaF9hdmdfZmluZXMpICsKICB0bV9maWxsKGNvbCA9ICJNZWFuIEZpbmVzIEFtb3VudCAkIikgKwogIHRtX2JvcmRlcnMoKSAKYGBgCgojIyMjIyBiKSBUeXBlcyBvZiB2aW9sYXRpb25zCgpHcm91cCB0aGUgYWxtb3N0IDEwMCB0eXBlcyBvZiB0aWNrZXQgdmlvbGF0aW9ucyBpbnRvIGEgc21hbGxlciBzZXQgb2YgNC02IHN1Ymdyb3VwcyAod2hlcmUgYG90aGVyYCBzaG91bGQgYmUgdGhlIHJlbWFpbmRlciBvZiB2aW9sYXRpb25zIG5vdCBpbmNsdWRlZCBpbiBvdGhlciBncm91cHMgeW91IGRlZmluZWQpLiBbSGludDogTm8gbmVlZCB0byBzcGVuZCBtb3JlIHRoYW4gNSBtaW51dGVzIHRoaW5raW5nIGFib3V0IHdoYXQgdGhlIHJpZ2h0IGdyb3VwaW5nIGlzLl0uIFByb3ZpZGUgY2hvcm9wbGV0aCBtYXBzIGZvciBlYWNoIG9mIHRoZXNlIHN1Ymdyb3VwcyB0byBzaG93IHdoZXJlIGRpZmZlcmVudCB0eXBlcyBvZiB2aW9sYXRpb25zIGFyZSBtb3JlIG9yIGxlc3MgY29tbW9uLiAKCmBgYHtyfQpwYXJraW5nX2xpc3QgPSBjKDk6MTEsIDEzOjI4LCAzMDozMywgMzc6MzksIDQ0LCA0NiwgNDcsIDU1LCA1OSwgNjAsIDYyOjY1LCA3NykKc3RpY2tlcl9saXN0ID0gYyg2ODo3NikKYGBgCgpgYGB7cn0KbTJiIDwtIG1uaF9kZiAlPiUKICBkcGx5cjo6c2VsZWN0KGBWaW9sYXRpb24gUHJlY2luY3RgLCBgVmlvbGF0aW9uIERlc2NyaXB0aW9uYCwgYFZpb2xhdGlvbiBDb2RlYCkgJT4lCiAgbXV0YXRlKAogICAgVHlwZSA9IGNhc2Vfd2hlbigKICAgICAgYFZpb2xhdGlvbiBDb2RlYCA9PSAxIHwgYFZpb2xhdGlvbiBDb2RlYCA9PSA0IHwgYFZpb2xhdGlvbiBDb2RlYCA9PSA1IHwgYFZpb2xhdGlvbiBDb2RlYCA9PSAxMiB8IGBWaW9sYXRpb24gQ29kZWAgPT0gMTggfCBgVmlvbGF0aW9uIENvZGVgID09IDE5IH4gIkJVUyByZWxhdGVkIHZpb2xhdGlvbnMiLAogICAgICBgVmlvbGF0aW9uIENvZGVgID09IDY3IHxgVmlvbGF0aW9uIENvZGVgPT0gNTAgfCBgVmlvbGF0aW9uIENvZGVgID09IDUxIHwgYFZpb2xhdGlvbiBDb2RlYCA9PSA0OCB+ICJWaW9sYXRpb25zIGFnYWluc3QgcGVkZXN0cmlhbiB0cmFmZmljIiwKICAgICAgYFZpb2xhdGlvbiBDb2RlYCAlaW4lIHBhcmtpbmdfbGlzdCB+ICJQYXJraW5nIHZpb2xhdGlvbnMiLAogICAgICBgVmlvbGF0aW9uIENvZGVgICVpbiUgc3RpY2tlcl9saXN0IH4gIlN0aWNrZXIgdmlvbGF0aW9ucyIsCiAgICAgIFRSVUUgfiAiT3RoZXIiCiAgICApCiAgKQpgYGAKCgpgYGB7cn0Kb3RoZXIgPC0gbTJiICU+JQogIGZpbHRlcihgVHlwZWAgPT0gIk90aGVyIikgJT4lCiAgZ3JvdXBfYnkoYFZpb2xhdGlvbiBQcmVjaW5jdGApICU+JQogIGNvdW50KCkgJT4lCiAgcmVuYW1lKGBPdGhlciBWaW9sYXRpb25zYCA9ICJuIikKYGBgCgpgYGB7cn0KcGVkZXN0cmlhbnMgPC0gbTJiICU+JQogIGZpbHRlcihgVHlwZWAgPT0gIlZpb2xhdGlvbnMgYWdhaW5zdCBwZWRlc3RyaWFuIHRyYWZmaWMiKSAlPiUKICBncm91cF9ieShgVmlvbGF0aW9uIFByZWNpbmN0YCkgJT4lCiAgY291bnQoKSAlPiUKICByZW5hbWUoYFZpb2xhdGlvbnMgdnMgUGVkZXN0cmlhbnNgID0gIm4iKQpgYGAKCmBgYHtyfQpwYXJraW5nIDwtIG0yYiAlPiUKICBmaWx0ZXIoYFR5cGVgID09ICJQYXJraW5nIHZpb2xhdGlvbnMiKSAlPiUKICBncm91cF9ieShgVmlvbGF0aW9uIFByZWNpbmN0YCkgJT4lCiAgY291bnQoKSAlPiUKICByZW5hbWUoYFBhcmtpbmcgVmlvbGF0aW9uc2AgPSAibiIpCmBgYAoKYGBge3J9CnN0aWNrZXIgPC0gbTJiICU+JQogIGZpbHRlcihgVHlwZWAgPT0gIlN0aWNrZXIgdmlvbGF0aW9ucyIpICU+JQogIGdyb3VwX2J5KGBWaW9sYXRpb24gUHJlY2luY3RgKSAlPiUKICBjb3VudCgpICU+JQogIHJlbmFtZShgU3RpY2tlciBWaW9sYXRpb25zYCA9ICJuIikKYGBgCgpgYGB7cn0KbW5oX21hcCA8LSBtZXJnZShtbmgsIG90aGVyLCBieS54ID0gIlByZWNpbmN0IiwgYnkueSA9ICJWaW9sYXRpb24gUHJlY2luY3QiLCBhbGwueCA9IFRSVUUsIGR1cGxpY2F0ZUdlb21zID0gVFJVRSkKbW5oX21hcCA8LSBtZXJnZShtbmhfbWFwLCBwZWRlc3RyaWFucywgYnkueCA9ICJQcmVjaW5jdCIsIGJ5LnkgPSAiVmlvbGF0aW9uIFByZWNpbmN0IiwgYWxsLnggPSBUUlVFLCBkdXBsaWNhdGVHZW9tcyA9IFRSVUUpCm1uaF9tYXAgPC0gbWVyZ2UobW5oX21hcCwgcGFya2luZywgYnkueCA9ICJQcmVjaW5jdCIsIGJ5LnkgPSAiVmlvbGF0aW9uIFByZWNpbmN0IiwgYWxsLnggPSBUUlVFLCBkdXBsaWNhdGVHZW9tcyA9IFRSVUUpCm1uaF9tYXAgPC0gbWVyZ2UobW5oX21hcCwgc3RpY2tlciwgYnkueCA9ICJQcmVjaW5jdCIsIGJ5LnkgPSAiVmlvbGF0aW9uIFByZWNpbmN0IiwgYWxsLnggPSBUUlVFLCBkdXBsaWNhdGVHZW9tcyA9IFRSVUUpCmBgYAoKYGBge3J9CnRtX290aGVyIDwtIHRtX3NoYXBlKG1uaF9tYXApICsKICB0bV9maWxsKGNvbCA9ICJPdGhlciBWaW9sYXRpb25zIikgKwogIHRtX2JvcmRlcnMoKSAKYGBgCgpgYGB7cn0KdG1fcGVkZSA8LSB0bV9zaGFwZShtbmhfbWFwKSArCiAgdG1fZmlsbChjb2wgPSAiVmlvbGF0aW9ucyB2cyBQZWRlc3RyaWFucyIpICsKICB0bV9ib3JkZXJzKCkgCmBgYAoKYGBge3J9CnRtX3BhcmtpbmcgPC0gdG1fc2hhcGUobW5oX21hcCkgKwogIHRtX2ZpbGwoY29sID0gIlBhcmtpbmcgVmlvbGF0aW9ucyIpICsKICB0bV9ib3JkZXJzKCkgCmBgYAoKYGBge3J9CnRtX3N0aWNrZXIgPC0gdG1fc2hhcGUobW5oX21hcCkgKwogIHRtX2ZpbGwoY29sID0gIlN0aWNrZXIgVmlvbGF0aW9ucyIpICsKICB0bV9ib3JkZXJzKCkgCmBgYAoKYGBge3J9CnRtYXBfYXJyYW5nZSh0bV9zdGlja2VyLCB0bV9wYXJraW5nLCB0bV9wZWRlLCB0bV9vdGhlcikKYGBgCgpfX18KIyMjIyAzLiBGb2N1cyBvbiB0aGUgVXBwZXIgRWFzdAoKW1ByZWNpbmN0IDE5XShodHRwczovL3d3dzEubnljLmdvdi9zaXRlL255cGQvYnVyZWF1cy9wYXRyb2wvcHJlY2luY3RzLzE5dGgtcHJlY2luY3QucGFnZSkgaWRlbnRpZmllcyB0aGUgVXBwZXIgRWFzdCBTaWRlLiBUaGUgZGF0YSBjdXJyZW50bHkgZG9lcyBub3QgcHJvdmlkZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIHRoZSB2aW9sYXRpb24gbG9jYXRpb25zIChhbmQgSSBhbSBub3Qgc3VyZSB3aGF0IHRoZXNlIGBzdHJlZXRfY29kZWAgdmFyaWFibGVzIGFyZSBmb3IpLgoKIyMjIyMgYSkgSWdub3JpbmcgZmlyZSBoeWRyYW50cwoKUmVzdHJpY3QgeW91ciBkYXRhIHRvIHBhcmtpbmcgdmlvbGF0aW9ucyByZWxhdGVkIHRvIGZpcmUgaHlkcmFudHMgKGBWaW9sYXRpb24gQ29kZSA9IDQwYCkuIFVzaW5nIHRoZSB2YXJpYWJsZXMgYFN0cmVldCBOYW1lYCBhbmQgYEhvdXNlIE51bWJlcmAgYXMgd2VsbCBhcyB0aGUga25vd2xlZGdlIHRoYXQgdGhlc2UgYWRkcmVzc2VzIGFyZSBpbiB0aGUgVXBwZXIgRWFzdCBTaWRlIG9mIE1hbmhhdHRhbiwgZ2VvY29kZSBhdCBsZWFzdCA1MDAgYWRkcmVzc2VzLiBJbmNsdWRlIGEgZGF0YSB0YWJsZSBvZiB0aGVzZSBhZGRyZXNzZXMgYW5kIHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIHRoZXNlIGFkZHJlc3NlcyBpbiB0aGUgb3V0cHV0LgoKYGBge3J9Cmdlb2xvY19kZiA8LSBtbmhfZGYgJT4lCiAgZmlsdGVyKGBWaW9sYXRpb24gUHJlY2luY3RgID09IDE5ICYgYFZpb2xhdGlvbiBDb2RlYCA9PSA0MCkgJT4lCiAgZHBseXI6OnNlbGVjdChgU3RyZWV0IE5hbWVgLCBgSG91c2UgTnVtYmVyYCwgYGlzc3VlX2RhdGVgLCBgVmlvbGF0aW9uIERlc2NyaXB0aW9uYCwgYEZpbmUgQW1vdW50ICRgLCBgUGxhdGUgSURgLCBgVmVoaWNsZSBNYWtlYCkKYGBgCgpgYGB7cn0KZ2VvbG9jX2RmJGBIb3VzZSBOdW1iZXJgIDwtIGFzLm51bWVyaWMoZ2VvbG9jX2RmJGBIb3VzZSBOdW1iZXJgKQpnZW9sb2NfZGYgPC0gZHJvcF9uYShnZW9sb2NfZGYpCgpnZW9sb2NfZGYgPC0gZ2VvbG9jX2RmICU+JQogIHVuaXRlKGBTdHJlZXQgTmFtZWAsIGBIb3VzZSBOdW1iZXJgLCBjb2wgPSAiTG9jYXRpb24iLCBzZXAgPSAiLCAiLCByZW1vdmUgPSBGQUxTRSkKCmdlb2xvY19kZiRMb2NhdGlvbiA8LSBwYXN0ZSgiVW5pdGVkIFN0YXRlcywgTlksIE5ldyBZb3JrLCIsIGdlb2xvY19kZiRMb2NhdGlvbiwgc2VwPSIgIikKCmhlYWQoZ2VvbG9jX2RmJExvY2F0aW9uKQoKZ2VvX2RmIDwtIGdlb2xvY19kZltzYW1wbGUobnJvdyhnZW9sb2NfZGYpLCA3MDApLCBdCmBgYAoKYGBge3J9CmV4YW1wbGUgPC0gYmFzZTo6c2FtcGxlKHggPSBnZW9fZGYkTG9jYXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUpCmV4YW1wbGUKZ2dtYXA6Omdlb2NvZGUobG9jYXRpb24gPSBleGFtcGxlKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KR2VvQ29kZWQgPC0gcHVycnI6Om1hcF9kZigueCA9IGdlb19kZiRMb2NhdGlvbiwgLmYgPSBnZ21hcDo6Z2VvY29kZSkKYGBgCgpgYGB7cn0KZ2VvY29kZWRfZGYgPC0gZHBseXI6OmJpbmRfY29scyhnZW9fZGYkTG9jYXRpb24sIEdlb0NvZGVkKSAlPiUgCiAgZHBseXI6OnNlbGVjdCgKICAgIGxuZyA9IGxvbiwKICAgIGxhdCwKICAgIGRwbHlyOjpldmVyeXRoaW5nKCkpCgpyZW5hbWUoZ2VvY29kZWRfZGYsICJBZGRyZXNzIiA9IGAuLi4xYCkKYGBgCgpgYGB7cn0KZGF0YXRhYmxlKGdlb2NvZGVkX2RmLAogICAgICAgICAgZmlsdGVyID0gJ3RvcCcsCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSwgYXV0b1dpZHRoID0gVFJVRSksCiAgICAgICAgICBjb2xuYW1lcyA9IGMoJ2xuZycsICdsYXQnLCAnQWRkcmVzcycpLAogICAgICAgICAgKQpgYGAKCiMjIyMjIGIpIEludGVyYWN0aXZlIE1hcAoKUHJvdmlkZSBhbiBpbnRlcmFjdGl2ZSBtYXAgb2YgdGhlIHZpb2xhdGlvbnMgeW91IGdlb2NvZGVkIHVzaW5nIGBsZWFmbGV0YC4gUHJvdmlkZSBhdCBsZWFzdCB0aHJlZSBwaWVjZXMgb2YgaW5mb3JtYXRpb24gb24gdGhlIHBhcmtpbmcgdGlja2V0IGluIGEgcG9wdXAuCgpgYGB7cn0KbGlicmFyeShsZWFmbGV0KQpgYGAKCmBgYHtyfQpnZW9fZGYgPC0gZHBseXI6OmJpbmRfY29scyhnZW9fZGYsIEdlb0NvZGVkKSAlPiUgCiAgZHBseXI6OnNlbGVjdCgKICAgIGxuZyA9IGxvbiwKICAgIGxhdCwKICAgIGRwbHlyOjpldmVyeXRoaW5nKCkpCmBgYAoKCmBgYHtyfQojIGNyZWF0aW5nIGEgcG9wdXB0ZXh0IGNvbHVtbgpnZW9fZGYgPC0gZ2VvX2RmICU+JSAgCiAgZHBseXI6Om11dGF0ZShwb3B1cHRleHQgPSBiYXNlOjpwYXN0ZTAoIjxiPiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfdG9fdGl0bGUoZ2VvX2RmJGBWaW9sYXRpb24gRGVzY3JpcHRpb25gKSwgIiBWaW9sYXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9iPjxiciAvPiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8aT4iLCAiRGF0ZTogIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvX2RmJGlzc3VlX2RhdGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiLCAiLCAiRmluZSBBbW91bnQ6ICQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9fZGYkYEZpbmUgQW1vdW50ICRgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9pPjxiciAvPiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8aT4iKSkKYGBgCgoKYGBge3J9CmxlYWZsZXQoZ2VvX2RmKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZENpcmNsZXMobG5nID0gfmxuZywgbGF0ID0gfmxhdCwgY29sb3IgPSAib3JhbmdlIiwgcG9wdXAgPSB+cG9wdXB0ZXh0KSAlPiUKICBzZXRWaWV3KGxuZyA9IC03My45NTYxODksIGxhdCA9IDQwLjc3NDkxNywgem9vbSA9IDE0KSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXIpCmBgYAoKIyMjIyMgYykgTHV4dXJ5IGNhcnMgYW5kIHJlcGVhdCBvZmZlbmRlcnMKClVzaW5nIHRoZSB2ZWhpY2xlIGBQbGF0ZSBJRGAsIGlkZW50aWZ5IHJlcGVhdCBvZmZlbmRlcnMgKGluIHRoZSBmdWxsIGRhdGFzZXQpLiBDcmVhdGUgYW5vdGhlciB2YXJpYWJsZSBjYWxsZWQgYGx1eHVyeV9jYXJgIGluIHdoaWNoIHlvdSBpZGVudGlmeSBsdXh1cnkgY2FyIGJyYW5kcyB1c2luZyB0aGUgYFZlaGljbGUgTWFrZWAgdmFyaWFibGUuCgpTdGFydCB3aXRoIHRoZSBwcmV2aW91cyBtYXAuIERpc3Rpbmd1aXNoIHRoZSBwb2ludHMgYnkgd2hldGhlciB0aGUgY2FyIGlzIGEgcmVwZWF0IG9mZmVuZGVyIGFuZC9vciBsdXh1cnkgY2FyLiBBZGQgYSBsZWdlbmQgaW5mb3JtaW5nIHRoZSB1c2VyIGFib3V0IHRoZSBjb2xvciBzY2hlbWUuIEFsc28gbWFrZSBzdXJlIHRoYXQgdGhlIGFkZGVkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBjYXIgdHlwZSBhbmQgcmVwZWF0IG9mZmVuZGVyIHN0YXR1cyBpcyBub3cgY29udGFpbmVkIGluIHRoZSBwb3B1cCBpbmZvcm1hdGlvbi4gU2hvdyB0aGlzIG1hcC4KCmBgYHtyfQpnaW5vIDwtIG1uaF9kZiAlPiUKICBkcGx5cjo6c2VsZWN0KGBQbGF0ZSBJRGApICU+JQogIGdyb3VwX2J5KGBQbGF0ZSBJRGApICU+JQogIGFkZF9jb3VudChgUGxhdGUgSURgKSAlPiUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUoYFJlcGVhdCBPZmZlbmRlcmAgPSBpZmVsc2UobiA+IDEsIHBhc3RlKCJZZXMiKSwgcGFzdGUoIk5vIikpKQpgYGAKCmBgYHtyfQpnaW5vICU+JQogIGFycmFuZ2UoZGVzYyhuKSkgJT4lCiAgaGVhZCgxMCkKYGBgCgpXb3chIEJlc2lkZXMgIkJMQU5LUExBVEUiLCBzb21lIG9mIHRoZXNlIHBlb3BsZSBjb3VsZCBoYXZlIGdvbmUgYnJva2UgZm9yIGFsbCB0aGVzZSBmaW5lcyEKCmBgYHtyfQpzb3J0KHVuaXF1ZShtbmhfZGYkYFZlaGljbGUgTWFrZWApKQpgYGAKYGBge3J9Cmx1eHVyeV9jYXJzID0gYygiTUFTRSIsICJGRVJSQSIsICJGRVIiLCAiQVNUTyIsICJST0xMUyIsICJNQVlCQSIsICJCVUdBVCIsICJCRU5UIiwgIlRFU0xBIiwgIlRFTFNBIiwgIlRTTEEiLCAiUE9SU0MiLCAiUk9MTEkiLCAiQVVESSIsICJCTVciLCAiTEFNQk8iLCAiTUVSQyIpCmBgYAoKYGBge3J9Cmdlb19kZiA8LSBnZW9fZGYgJT4lCiAgbXV0YXRlKAogICAgYENhciBUeXBlYCA9IGNhc2Vfd2hlbigKICAgIGBWZWhpY2xlIE1ha2VgICVpbiUgbHV4dXJ5X2NhcnMgfiAiTHV4dXJ5IGNhciIsCiAgICAgIFRSVUUgfiAiTm9uLWx1eHVyeSBjYXIiCiAgKSkKYGBgCgpgYGB7cn0KZ2VvX2RmIDwtIGxlZnRfam9pbihnZW9fZGYsIGdpbm8sIGJ5ID0gIlBsYXRlIElEIiwgYWxsLnggPSBUUlVFKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgpgYGB7cn0KcGFsID0gY29sb3JGYWN0b3IoIlNldDEiLCBkb21haW4gPSBnZW9fZGYkYFJlcGVhdCBPZmZlbmRlcmApCnJwb2ZfY29sb3IgPSBwYWwoZ2VvX2RmJGBSZXBlYXQgT2ZmZW5kZXJgKQpgYGAKCmBgYHtyfQpwb3B1cCA8LSBwYXN0ZSgiPGI+IiwgIlBsYXRlIElEOiAiLCAiPC9iPiIsIGdlb19kZiRgUGxhdGUgSURgLCAiPGJyLz4iLAogICAgICAgICAgICAgICAiPGI+IiwiQ2FyIFR5cGU6ICIsICI8L2I+IiwgZ2VvX2RmJGBDYXIgVHlwZWAsICI8YnIvPiIsCiAgICAgICAgICAgICAgICI8Yj4iLCJSZXBlYXQgT2ZmZW5kZXI6ICIsICI8L2I+IiwgZ2VvX2RmJGBSZXBlYXQgT2ZmZW5kZXJgLCAiPGJyLz4iKQpgYGAKCmBgYHtyfQpsZWFmbGV0KGdlb19kZikgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRDaXJjbGVzKGxuZyA9IH5sbmcsIGxhdCA9IH5sYXQsIGNvbG9yID0gcnBvZl9jb2xvciwgcG9wdXAgPSB+cG9wdXApICU+JQogIGFkZExlZ2VuZChwYWwgPSBwYWwsIHZhbHVlcyA9IH5nZW9fZGYkYFJlcGVhdCBPZmZlbmRlcmAsIHRpdGxlID0gIlJlcGVhdCBPZmZlbmRlciIpICU+JQogIHNldFZpZXcobG5nID0gLTczLjk1NjE4OSwgbGF0ID0gNDAuNzc0OTE3LCB6b29tID0gMTQpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlcikKYGBgCgojIyMjIyBkKSBDbHVzdGVyCgpBZGQgbWFya2VyIGNsdXN0ZXJpbmcsIHNvIHRoYXQgem9vbWluZyBpbiB3aWxsIHJldmVhbCB0aGUgaW5kaXZpZHVhbCBsb2NhdGlvbnMgYnV0IHRoZSB6b29tZWQgb3V0IG1hcCBvbmx5IHNob3dzIHRoZSBjbHVzdGVycy4gU2hvdyB0aGUgbWFwIHdpdGggY2x1c3RlcnMuCgpgYGB7cn0KbGVhZmxldChnZW9fZGYpICU+JQogIGFkZFRpbGVzKCkgJT4lCiAgYWRkQ2lyY2xlTWFya2Vycyhjb2xvciA9IHJwb2ZfY29sb3IsIHBvcHVwID0gcG9wdXAsIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfmdlb19kZiRgUmVwZWF0IE9mZmVuZGVyYCwgdGl0bGUgPSAiUmVwZWF0IE9mZmVuZGVyIikgJT4lCiAgc2V0VmlldyhsbmcgPSAtNzMuOTU2MTg5LCBsYXQgPSA0MC43NzQ5MTcsIHpvb20gPSAxNCkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyKQpgYGA=